<?php

/**
 * MYDAILYPHOTO API PHP SDK (http://mydailyphoto.com/developer)
 *
 * @version 1.0.0 (27 MARCH 2010)
 * @author Matthew Congrove <matt@mydailyphoto.com>
 * @copyright Copyright (c) 2010, MyDailyPhoto, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

class MyDailyPhoto
{
	const ERROR_API_ERROR = 1;
	const ERROR_INVALID_PROPERTY = 2;
	const ERROR_KEY_NOT_PROVIDED = 3;
	const ERROR_API_TRANSACTION_FAILED = 4;
	const ERROR_INVALID_METHOD = 5;

	private $api_url = 'dev.mydailyphoto.com/api/?';
	private $bit32 = FALSE;
	private $key = NULL;

	private $methods = array(
		'getAuth',
		'getCommentsPhoto',
		'getCommentsUser',
		'getFriends',
		'getMessage',
		'getMissedDates',
		'getPhoto',
		'getPhotos',
		'getSearch',
		'getUser'
	);

	/**
	 * The constructor for the MyDailyPhoto class.
	 *
	 * @access Public
	 * @since 1.0.0
	 * @param string  [$key] The API key for the application
	 */
	public function __construct($key)
	{
		$this->key = $key;
		$this->bit32 = ((string)'99999999999999' == (int)'99999999999999') ? FALSE : TRUE;
	}

	/**
	 * Sets a property of the MyDailyPhoto class.
	 *
	 * @access Public
	 * @since 1.0.0
	 * @param string  [$key] The property to set
	 * @param mixed   [$value] The new value for the property
	 * @return mixed The new value of the property
	 */
	public function __set($key, $value)
	{
		if (isset($this->$key) || is_null($this->$key)) {
			$this->$key = $value;
		} else {
			throw new ErrorInvalidProperty($this, self::ERROR_INVALID_PROPERTY);
		}
	}

	/**
	 * Retrieves a property of the MyDailyPhoto class.
	 *
	 * @access Public
	 * @since 1.0.0
	 * @param string  [$key] The property to retrieve
	 * @return mixed  The value of the property
	 */
	public function __get($key)
	{
		if (isset($this->$key) || is_null($this->$key)) {
			return $this->$key;
		} else {
			throw new ErrorInvalidProperty($this, self::ERROR_INVALID_PROPERTY);
		}
	}

	/**
	 * Formats the request for API methods and retrieves the data.
	 *
	 * @access Public
	 * @since 1.0.0
	 * @param string  [$call] The requested API method
	 * @param mixed   [$params] An array of API parameters, or a single value that matches the default
	 * @return object An object containing all API return data
	 */
	public function req($method, $params)
	{
		if (!isset($this->methods[$method])) {
			throw new ErrorInvalidMethod($this, self::ERROR_INVALID_METHOD);
		}

		if (!isset($params)) {
			$params = array();
		} else {
			if (!is_array($params)) {
				$temp = $params;

				$params = array();
				$params[] = $temp;
			}
		}

		if ($method == 'getAuth') {
			$username = $params['username'];
			$password = $params['password'];
			$params = array();

			$url = $this->appendParams($method, $params);

			$return = $this->getData('http://' . $username . ':' . $password . '@' . $this->api_url . $url);
		} else {
			$url = $this->appendParams($method, $params);

			$return = $this->getData('http://' . $this->api_url . $url);
		}

		return $return;
	}

	/**
	 * Appends API parameters onto API request URL.
	 *
	 * @access Private
	 * @since 1.0.0
	 * @param string  [$method] The requested API method
	 * @param array   [$params] An array of API parameters
	 * @return string The complete API request URL
	 */
	private function appendParams($method, $params = NULL)
	{
		$url = 'key=' . $this->key . '&command=' . $method;

		if (isset($params)) {
			foreach ($params as $option => $value) {
				$url .= '&' . $option . '=' . urlencode($value);
			}
		}

		return $url;
	}

	/**
	 * Retrieves API data from provided URL.
	 *
	 * @access Private
	 * @since 1.0.0
	 * @param string  [$url] The complete API request URL
	 * @return object An object containing all API return data
	 */
	private function getData($url)
	{
		if (!isset($this->key)) {
			throw new ErrorKeyError($this, self::ERROR_KEY_NOT_PROVIDED);
		}

		$response = $this->curlRequest($url, TRUE);

		if ($response && $response != 'NULL') {
			$response_object = json_decode($response);

			if (is_array($response_object) && isset($response_object[0]->errorCode)) {
				throw new ErrorApiError($this, self::ERROR_API_ERROR, $response_object[0]);
			} else {
				if (isset($response_object->items)) {
					$data = $response_object->items;
				} else {
					$data = $response_object;
				}

				return $data;
			}
		} else {
			throw new ErrorApiError($this, self::ERROR_API_ERROR);
		}
	}


	/**
	 * Sends data to the API.
	 *
	 * @access Private
	 * @since 1.0.0
	 * @param array   [$request] The data to send
	 * @return object An object containing all API return data
	 */
	private function putData($request, $return_json = TRUE)
	{
		if (!isset($this->key)) {
			throw new ErrorKeyError($this, self::ERROR_KEY_NOT_PROVIDED);
		}

		$response = $this->curlRequest($request, FALSE);

		if ($return_json) {
			$response_object = json_decode($response);

			if (!isset($response_object->result)) {
				throw new ErrorApiError($this, self::ERROR_API_ERROR, $response_object[0]);
			}
		}

		return $response_object;
	}

	/**
	 * Makes a cURL request.
	 *
	 * @access Private
	 * @since 1.0.0
	 * @param mixed   [$request] URL to fetch or the data to send via POST
	 * @param boolean [$get_request] If false, send POST params
	 */
	private function curlRequest($request, $get_request = FALSE)
	{
		$curl = curl_init();

		if ($get_request) {
			curl_setopt($curl, CURLOPT_URL, $request);
		} else {
			curl_setopt($curl, CURLOPT_URL, 'https://' . $this->api_url . '/' . $this->key . '/');
			curl_setopt($curl, CURLOPT_POST, 1);
			curl_setopt($curl, CURLOPT_POSTFIELDS, $request);
		}

		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
		$response = curl_exec($curl);

		$curl_error = NULL;

		if (curl_errno($curl)) {
			$curl_error = curl_error($curl);
		}

		curl_close($curl);

		if ($curl_error !== NULL) {
			if ($get_request) {
				throw new ErrorTransactionError($this, self::ERROR_API_TRANSACTION_FAILED, $curl_error);
			} else {
				throw new ErrorTransactionError($this, self::ERROR_API_TRANSACTION_FAILED, $curl_error);
			}
		}

		return $this->bit32clean($response);
	}

	/**
	 * Cleans the response for 32-bit machine compliance.
	 *
	 * @access Private
	 * @since 1.0.0
	 * @param string  [$response] The response from a cURL request
	 * @return string The cleansed string if using a 32-bit machine.
	 */
	private function bit32Clean($response)
	{
		if ($this->bit32) {
			$response = preg_replace('/:\s*(\d{10,})/', ':"$1"', $response);
			$response = preg_replace('/(\d{10,})\]/', '"$1"]', $response);
			$response = preg_replace('/(\d{10,})\,/', '"$1",', $response);
		}

		return $response;
	}

	/**
	 * Converts an error code into a textual representation.
	 *
	 * @access public
	 * @since 1.0.0
	 * @param int     [$err_code] The code number of an error
	 * @return string The error text
	 */
	public function getErrorAsString($code)
	{
		switch ($code) {
		case self::ERROR_API_ERROR:
			return 'API error';
			break;
		case self::ERROR_INVALID_METHOD:
			return 'Requested method not found';
			break;
		case self::ERROR_INVALID_PROPERTY:
			return 'Requested property not found';
			break;
		case self::ERROR_API_TRANSACTION_FAILED:
			return 'API transaction failed';
			break;
		case self::ERROR_KEY_NOT_PROVIDED:
			return 'Application key not provided';
			break;
		}
	}
}

class MyDailyPhotoException extends Exception
{
	/**
	 * The constructor for the MyDailyPhotoException class
	 *
	 * @access Public
	 * @since 1.0.0
	 * @param object  [$obj] A pointer to the MyDailyPhoto class
	 * @param int     [$code] The error code
	 * @param string  [$raw] Any additional error information
	 */
	public function __construct(MyDailyPhoto $obj, $code, $raw = NULL)
	{
		$error = $obj->getErrorAsString($code);

		if (isset($raw)) {
			$error .= "'.\n" . 'API Error Code #' . $raw->errorCode . ': ' . $raw->errorMessage . "\n";
		}

		parent::__construct($error, $code);
	}
}

class ErrorApiError extends MyDailyPhotoException{}
class ErrorIdNotProvided extends MyDailyPhotoException{}
class ErrorInvalidMethod extends MyDailyPhotoException{}
class ErrorInvalidProperty extends MyDailyPhotoException{}
class ErrorInvalidFileType extends MyDailyPhotoException{}
class ErrorInvalidType extends MyDailyPhotoException{}
class ErrorKeyError extends MyDailyPhotoException{}
class ErrorTransactionError extends MyDailyPhotoException{}
class ErrorDtoDoesNotExist extends MyDailyPhotoException{}

?>